package com.gengzp.common.interceptor;

import com.fasterxml.jackson.core.type.TypeReference;
import com.gengzp.common.enums.YesNoEnum;
import com.gengzp.common.model.base.entity.GenericEntity;
import com.gengzp.common.model.pagination.req.PaginationReq;
import com.gengzp.common.utils.JsonUtils;
import com.gengzp.common.utils.SnowflakeIdGenerator;
import com.gengzp.permission.utils.PermissionUtils;
import com.google.common.collect.Lists;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ClassName MyBatisInterceptor
 * @Description mybatis拦截器, 自定义字段自动填充和分页查询
 * @Author gengzp
 * @Date 2025/5/11 15:07
 */
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MyBatisInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(MyBatisInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 获取拦截的 StatementHandler 对象
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 解除包装,获取原始的 MetaObject 对象
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        // 获取当前的参数
        Object parameter = statementHandler.getParameterHandler().getParameterObject();
        if (Objects.isNull(parameter)) {
            return invocation.proceed();
        }

        // 获取当前的 sql 信息
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");

        // 分页查询处理逻辑
        if (checkIsPaginationQuery(boundSql, parameter)) {
            return handlePaginationQuery(invocation, metaObject, boundSql);
        }
        // 写操作自动填充处理逻辑
        if (checkIsWriteOperation(boundSql, parameter)) {
            return handleAutoFillFields(invocation, boundSql);
        }

        // 打印sql日志
        String paramsStr = "";
        if (boundSql.getParameterObject() instanceof Map<?, ?>) {
            Map<String, Object> sqlParamMap = ((Map<String, Object>) boundSql.getParameterObject());
            String fieldName = sqlParamMap.keySet().iterator().next();
            paramsStr = String.format("{\"%s\":\"%s\"}", fieldName, JsonUtils.toJsonString(sqlParamMap.get(fieldName)));
        } else {
            paramsStr = JsonUtils.toJsonString(boundSql.getParameterObject());
        }

        long sqlStartTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        logger.info("""
                         
                         
                         ===================== 【开始执行sql】 =====================\s
                            【sql语句】:
                        {}
                            【sql参数】:
                        {}
                         ===================== 【sql执行完成，sql执行耗时：{}ms】 =====================\s
                        """,
                boundSql.getSql().replaceAll("(\\r?\\n|\\r)", " ").replaceAll(" +", " ").trim(),
                paramsStr, System.currentTimeMillis() - sqlStartTime);
        return result;
    }

    /**
     * 校验当前执行的 sql 是否为分页查询语句
     *
     * @param boundSql  sql信息
     * @param parameter 参数对象
     */
    private boolean checkIsPaginationQuery(BoundSql boundSql, Object parameter) {
        return boundSql.getSql().toLowerCase().trim().startsWith("select") && parameter instanceof PaginationReq;
    }

    /**
     * 校验当前执行的 sql 是否为写操作
     *
     * @param boundSql  sql信息
     * @param parameter 参数对象
     */
    private boolean checkIsWriteOperation(BoundSql boundSql, Object parameter) {
        // 转换sql语句为小写字符, 且去掉前后空格, 用于后续校验
        String sql = boundSql.getSql().toLowerCase().trim();

        // 先校验是否为写操作
        if (!sql.startsWith("insert") && !sql.startsWith("update")) {
            return false;
        }

        // 若参数为基本实体类, 则直接通过校验
        if (parameter instanceof GenericEntity) {
            return true;
        }

        // 处理参数为Map的情况
        if (parameter instanceof Map<?, ?> parameterMap) {

            // 检查Map中是否有GenericEntity类型的value
            for (Object value : parameterMap.values()) {
                if (value instanceof GenericEntity) {
                    return true;
                }

                // 处理MyBatis将List封装到Map的情况（key通常为"list"或"collection"）
                if (value instanceof List<?> list) {
                    if (!list.isEmpty() && list.get(0) instanceof GenericEntity) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     * 处理分页查询逻辑
     */
    private Object handlePaginationQuery(Invocation invocation, MetaObject metaObject, BoundSql boundSql) throws Exception {
        // 计数查询, 获取数据总条数
        int total = handlePaginationCount(invocation, metaObject, boundSql);
        // 分页查询参数 PaginationReq 对象
        PaginationReq parameter = (PaginationReq) boundSql.getParameterObject();
        // 计算总页数
        int pageCount = !Objects.equals(total, 0) ? total / parameter.getPageSize() + (Objects.equals(total % parameter.getPageSize(), 0) ? 0 : 1) : 0;

        // 计算偏移量
        int offset = (parameter.getPageNum() - 1) * parameter.getPageSize();
        //组装分页查询sql语句
        String pageQuerySql = boundSql.getSql() + " limit " + offset + " , " + parameter.getPageSize();
        metaObject.setValue("delegate.boundSql.sql", pageQuerySql);

        // 执行分页查询语句
        long sqlStartTimeMillis = System.currentTimeMillis();
        Object proceed = invocation.proceed();
        // 打印sql日志
        Map<String, Object> parametersMap = JsonUtils.parseObject(JsonUtils.toJsonString(parameter), new TypeReference<>() {
        });
        logger.info("""
                         
                         
                         ===================== 【开始执行分页查询sql】 =====================\s
                            【sql语句】:
                        {}
                            【分页查询参数】:
                        {}
                         ===================== 【分页查询sql执行完成， sql执行耗时：{} ms】 =====================\s
                         """,
                pageQuerySql.replaceAll("(\\r?\\n|\\r)", " ").replaceAll(" +", " ").trim(),
                JsonUtils.toJsonString(boundSql.getParameterObject()), System.currentTimeMillis() - sqlStartTimeMillis);

        // 将计数查询结果设置到分页查询参数 PaginationReq 对象中
        parameter.setTotal(total);
        parameter.setPageCount(pageCount);
        return proceed;
    }

    /**
     * 处理计数查询逻辑, 获取当前查询语句对应的数据总条数
     */
    private Integer handlePaginationCount(Invocation invocation, MetaObject metaObject, BoundSql boundSql) {
        // 默认数据总条数: 0
        int total = 0;
        // 原始sql
        String originalSql = boundSql.getSql();

        // 将原始sql转为消息
        String lowerCaseOriginalSql = originalSql.toLowerCase();
        // 如果存在 group by, 则需要将 group by 后面的字段去除,否则会导致 count 查询结果不正确
        if (lowerCaseOriginalSql.contains("group by")) {
            lowerCaseOriginalSql = lowerCaseOriginalSql.substring(0, lowerCaseOriginalSql.indexOf("group by"));
        }

        // 组装计数查询sql
        String countSql = "select count(*) from ( " + lowerCaseOriginalSql + " ) as tmp";
        metaObject.setValue("delegate.boundSql.sql", countSql);

        // 获取当前数据会话
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // 获取当前数据库连接
        Connection connection = (Connection) invocation.getArgs()[0];
        // 获取mapper传递的参数对象
        Object parameterObject = boundSql.getParameterObject();

        // 执行计数查询
        try (PreparedStatement ps = connection.prepareStatement(countSql)) {
            //设置参数
            DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
            parameterHandler.setParameters(ps);

            //执行查询sql
            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    total = rs.getInt(1);
                }
            }
        } catch (Exception e) {
            logger.info("分页计数查询失败: ", e);
        } finally {
            // 执行分页查询后, 需要将原sql组装回去
            metaObject.setValue("delegate.boundSql.sql", originalSql);
        }

        return total;
    }

    /**
     * 处理自动填充字段逻辑
     */
    private Object handleAutoFillFields(Invocation invocation, BoundSql boundSql) throws Exception {
        // 获取当前登录用户的id
        Long currentUserId = Long.valueOf(PermissionUtils.getCurrentUserId());
        // 获取当前系统时间
        Date currentSystemTime = new Date();
        //判断当前操作类型, 以对不同的字段填充处理
        Boolean isInsert = boundSql.getSql().toLowerCase().startsWith("insert");

        // 获取当前方法的参数 (约定大于配置: 所有需要自动填充的写操作, 只允许传一个参数, 因此直接取参数对象进行处理)
        Object parameterObject = boundSql.getParameterObject();

        // 参数是集合类型, 则需要对每个参数都进行字段填充
        if (parameterObject instanceof Collection<?>) {
            ((List<?>) parameterObject).forEach(e -> doAutoFill(e, currentUserId, currentSystemTime, isInsert));
        } else if (parameterObject instanceof Map<?, ?>) {
            // 参数被 myBatis 处理成了map, 则需要对value中的所有list类型的每个元素进行填充
            ((Map<?, ?>) parameterObject).values().forEach(e -> {
                if (e instanceof List<?> && ((List<?>) e).get(0) instanceof GenericEntity) {
                    ((List<?>) e).forEach(listElement -> doAutoFill(listElement, currentUserId, currentSystemTime, isInsert));
                }
            });
        } else {
            // 参数是对象类型, 则直接对当前参数进行字段填充
            doAutoFill(parameterObject, currentUserId, currentSystemTime, isInsert);
        }

        long sqlStartTimeMillis = System.currentTimeMillis();
        Object result = invocation.proceed();
        long sqlTimeConsuming = System.currentTimeMillis() - sqlStartTimeMillis;
        // 打印sql日志
        Map<String, Object> parametersMap = JsonUtils.parseObject(JsonUtils.toJsonString(parameterObject), new TypeReference<>() {
        });
        if (parameterObject instanceof GenericEntity) {
            logger.info("""
                                                        
                                                        
                             ===================== 【开始执行 {} 操作sql】 =====================
                                【sql语句】:
                            {}
                                【sql参数】:
                            {}
                             ===================== 【{} 操作sql执行完成，sql执行耗时：{}ms】 =====================\s
                            """,
                    isInsert ? "插入" : "编辑",
                    boundSql.getSql().replaceAll("(\\r?\\n|\\r)", " ").replaceAll(" +", " ").trim(),
                    boundSql.getParameterMappings().stream()
                            .map(v -> Optional.ofNullable(parametersMap.get(v.getProperty())).map(Object::toString).orElse("null"))
                            .collect(Collectors.joining(", ")), isInsert ? "插入" : "编辑", sqlTimeConsuming);
        } else if (parameterObject instanceof Map<?, ?>) {
            logger.info("""
                             
                             
                             ===================== 【开始执行 批量{} 操作sql】 =====================\s
                                【执行批量操作sql语句】:
                            {}
                                【sql参数】:
                            {}
                             ===================== 【批量{} 操作sql执行完成，sql执行耗时：{}ms】 =====================
                            """,
                    isInsert ? "插入" : "编辑",
                    boundSql.getSql().replaceAll("(\\r?\\n|\\r)", " ").replaceAll(" +", " ").trim(),
                    getCollectionFromMapParam(parameterObject).stream().map(JsonUtils::toJsonString).collect(Collectors.joining("; ")),
                    isInsert ? "插入" : "编辑", sqlTimeConsuming);
        }

        return result;
    }

    /**
     * 进行写操作字段自动填充
     *
     * @param entity            写操作参数对象
     * @param currentUserId     当前操作用户id
     * @param currentSystemTime 当前系统时间
     * @param isInsert          是否是插入操作标识
     */
    private void doAutoFill(Object entity, Long currentUserId, Date currentSystemTime, Boolean isInsert) {
        try {
            // 填充 最后一次修改人, 若当前登录用户为空, 则不填充
            if (Objects.nonNull(currentUserId)) {
                Method setUpdateBy = entity.getClass().getMethod("setLastModifiedBy", Long.class);
                setUpdateBy.invoke(entity, currentUserId);
            }

            // 填充 最后一次修改时间
            Method setUpdateTime = entity.getClass().getMethod("setLastModifiedTime", Date.class);
            setUpdateTime.invoke(entity, currentSystemTime);

            //如果当前操作为插入操作,则额外填充 主键id,创建人,创建时间,逻辑删除标识 字段
            if (isInsert) {
                // 填充 主键id, 使用雪花算法生成唯一id, 若业务中已经手动为id填充, 则不覆盖原有值
                if (Objects.isNull(((GenericEntity) entity).getId())) {
                    Method setId = entity.getClass().getMethod("setId", Long.class);
                    setId.invoke(entity, SnowflakeIdGenerator.generateId());
                }

                // 填充 创建人, 若当前登录用户为空, 则不填充
                if (Objects.nonNull(currentUserId)) {
                    Method setCreateBy = entity.getClass().getMethod("setCreatedBy", Long.class);
                    setCreateBy.invoke(entity, currentUserId);
                }

                // 填充创建时间
                Method setCreateTime = entity.getClass().getMethod("setCreatedTime", Date.class);
                setCreateTime.invoke(entity, currentSystemTime);

                // 填充逻辑删除
                Method setIsDelete = entity.getClass().getMethod("setIsDeleted", Integer.class);
                setIsDelete.invoke(entity, YesNoEnum.NO.getCode());
            }
        } catch (Exception e) {
            logger.error(String.format("字段自动填充失败,异常信息: %s", e.getMessage()));
        }
    }

    /**
     * 从Map类型的参数中拿到其中的list集合
     *
     * @return list集合
     */
    public List<?> getCollectionFromMapParam(Object parameterObject) {
        for (Map.Entry<?, ?> entry : ((Map<?, ?>) parameterObject).entrySet()) {
            if (entry.getValue() instanceof Collections) {
                return (List<?>) entry.getValue();
            }
        }
        return Lists.newArrayList();
    }

}
